import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Scanner;

/**
 * @author fengbo
 */
public class ClientB {

    private BufferedReader inPunch;
    private BufferedOutputStream outPunch;

    private int localPort;
    private int remotePort;

    public ClientB(InetAddress ip, int serverPort) throws IOException{

        Socket socket = new Socket(ip, serverPort);

        BufferedReader inPunch = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        BufferedOutputStream outPunch = new BufferedOutputStream(socket.getOutputStream());

        System.out.println("准备从服务端读取数据");
        readForServer(inPunch);

        System.out.println("发送wait等待，conn获取可连接的服务器");
        while (true) {
            Scanner scanner = new Scanner(System.in);
            String in = scanner.nextLine();
            if ("wait".equals(in)) {
                localPort = 19998;
                remotePort = 19999;
            } else if ("conn".equals(in)) {
                localPort = 19999;
                remotePort = 19998;
            }
            outPunch.write(in.getBytes());
            outPunch.write('\n');
            outPunch.flush();
        }
    }

    private void readForServer(BufferedReader inPunch) {
        new Thread(() -> {
            while (true){
                try{
                    String message = inPunch.readLine();
                    System.out.println(message);
                    if (!message.startsWith("ok::")) {
                        continue;
                    }

                    String[] tokens = message.split("::");

                    System.out.println("****************************************");
                    System.out.println("远程IP: " + tokens[1]);
                    System.out.println("本地IP: " + tokens[2]);
                    System.out.println("****************************************");

                    proceedHolePunching(tokens[1].trim());
                }catch (IOException ioe){
                    ioe.printStackTrace();
                }
            }
        }).start();
    }

    private void proceedHolePunching(String address) throws IOException{
        new Thread(() -> {
            try {
                Socket socket = new Socket();
                socket.setReuseAddress(true);

                socket.bind(new InetSocketAddress(localPort));

                listenPort();

                socket.connect(new InetSocketAddress(address, remotePort));

                inPunch = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                outPunch = new BufferedOutputStream(socket.getOutputStream());

                File[] dirList = File.listRoots();
                outPunch.write("CCCC".getBytes());
                outPunch.flush();
                for (File file : dirList) {
                    outPunch.write(file.getAbsolutePath().getBytes());
                    outPunch.write('\n');
                    outPunch.flush();
                }

                listenDataOnHole(address, remotePort);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void listenPort() {
        new Thread(() -> {
            try{
                System.out.println("Listen hole on port: " + localPort);
                ServerSocket socketServerPunch = new ServerSocket(localPort);
                socketServerPunch.accept();
            } catch (Exception e){
                outPunch = null;
                inPunch = null;
            }
        }).start();
    }

    private void listenDataOnHole(String addr, int port) throws IOException {
        while(true) {
            String message = inPunch.readLine();
            if (message.startsWith("aaaa")) {
                File file = new File(message.substring(4));
                long len = file.length();
                System.out.println(len + "====================");
                outPunch.write("BBBB".getBytes());
                outPunch.flush();
                outPunch.write(long2Byte(len));
                outPunch.flush();
                FileInputStream inputStream = new FileInputStream(file);
                byte[] buff = new byte[1024];
                int read;
                while ((read = inputStream.read(buff)) > 0) {
                    outPunch.write(buff);
                    outPunch.flush();
                }
                inputStream.close();
                continue;
            }
            listFile(message);
            System.out.println("Received: " + message.trim() + ", From: IP " + addr + " Port " + port);
        }
    }

    private byte[] long2Byte(long x) {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.putLong(0, x);
        return buffer.array();
    }
    
    private void listFile(String filename){
        File file = new File(filename);
        if (!file.exists() || !file.isDirectory()) {
            return;
        }
        File[] dirList = file.listFiles();
        if (dirList != null && dirList.length > 0) {
            try {
                outPunch.write("DDDD".getBytes());
                outPunch.flush();
                for (File value : dirList) {
                    if (value.isDirectory()) {
                        outPunch.write(("!@#" + value.getAbsolutePath()).getBytes());
                    } else {
                        outPunch.write(value.getAbsolutePath().getBytes());
                    }
                    outPunch.write('\n');
                    outPunch.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException{
        new ClientB(InetAddress.getByName("121.36.12.15"),  8088);
    }
}
